// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 此文件实现方法集。

package types

import (
	"fmt"
	"sort"
	"strings"
)

// 方法集是一组有序的具体或抽象（接口）方法；
// 方法是MethodVal选择，它们按升序m.Obj（）排序。Id（）。
// 方法集的零值是一个随时可用的空方法集。
type MethodSet struct {
	list []*Selection
}

func (s *MethodSet) String() string {
	if s.Len() == 0 {
		return "MethodSet {}"
	}

	var buf strings.Builder
	fmt.Fprintln(&buf, "MethodSet {")
	for _, f := range s.list {
		fmt.Fprintf(&buf, "\t%s\n", f)
	}
	fmt.Fprintln(&buf, "}")
	return buf.String()
}

// Len返回s中的方法数。
func (s *MethodSet) Len() int { return len(s.list) }

// At返回s中0<=i<s.Len（）的第i个方法。wen jian defg
func (s *MethodSet) At(i int) *Selection { return s.list[i] }

func (s *MethodSet) Lookup(pkg *Package, name string) *Selection {
	if s.Len() == 0 {
		return nil
	}

	key := Id(pkg, name)
	i := sort.Search(len(s.list), func(i int) bool {
		m := s.list[i]
		return m.obj.Id() >= key
	})
	if i < len(s.list) {
		m := s.list[i]
		if m.obj.Id() == key {
			return m
		}
	}
	return nil
}

// 共享空方法集。
var emptyMethodSet MethodSet

// 注意：NewMethodSet仅供外部使用，因为它要求接口完整。如果LookupFieldOrMethod事先完成了相同的
// 。
// 接口，则可以在内部使用

// NewMethodSet返回给定类型T的方法集。
// 它始终返回非nil方法集，即使它是空的。
func NewMethodSet(T Type) *MethodSet {
	// 警告：此函数中的代码非常微妙-不要随意修改！
	// 此函数和lookupFieldOrMethod应保持同步。

	// TODO（rfindley）确认此代码与lookupFieldOrMethod 
	// 。

	// 方法设置到当前深度，延迟分配
	var base methodSet

	typ, isPtr := deref(T)

	// /*typ，其中typ是一个接口，没有方法。
	if isPtr && IsInterface(typ) {
		return &emptyMethodSet
	}

	// 从最浅深度的单一入口开始。
	current := []embeddedType{{typ, nil, isPtr, false}}

	// 我们已经看到的命名类型，被懒洋洋地分配。
	// 用于在递归类型的情况下避免无休止的搜索。
	// 由于递归类型只能使用命名类型，我们
	// 只需要跟踪这些类型。
	// （如果我们允许类型别名构造递归类型，
	// 我们必须使用类型标识而不是指针相等来进行
	// 映射键比较，就像我们在consolidateMultiples中所做的那样。）
	var seen map[*Named]bool

	// 在当前深度收集方法
	for len(current) > 0 {
		var next []embeddedType // 在当前深度找到的嵌入类型

		// 在当前深度按名称（Id）索引并延迟分配的字段和方法集
		var fset map[string]bool // 我们只关心字段名
		var mset methodSet

		for _, e := range current {
			typ := e.typ

			// 如果我们有一个命名类型，我们可能有关联的方法。
			// 首先寻找那些。
			if named, _ := typ.(*Named); named != nil {
				if seen[named] {
					// 我们以前见过这种类型，在更浅的深度
					// （请注意，在当前深度
					// 这种类型的倍数以前是合并的）。该深度处的类型会在当前深度处阴影
					// 这个相同的类型，所以我们可以忽略
					// 这个。
					continue
				}
				if seen == nil {
					seen = make(map[*Named]bool)
				}
				seen[named] = true

				for i := 0; i < named.NumMethods(); i++ {
					mset = mset.addOne(named.Method(i), concat(e.index, i), e.indirect, e.multiples)
				}
			}

			switch t := under(typ).(type) {
			case *Struct:
				for i, f := range t.fields {
					if fset == nil {
						fset = make(map[string]bool)
					}
					fset[f.Id()] = true

					// 嵌入字段的格式始终为T或*T，其中
					// T是类型名。如果typ在
					// 这个深度出现多次，f.Type在下一个
					// 深度出现多次。
					if f.embedded {
						typ, isPtr := deref(f.typ)
						// TODO（gri）优化：忽略不能使用字段或方法的类型（只需考虑命名、结构和
						// 接口类型）。
						next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples})
					}
				}

			case *Interface:
				mset = mset.add(t.typeSet().methods, e.index, true, e.multiples)
			}
		}

		// 如果尚未存在具有匹配
		// 名称的条目，请在此深度向base添加方法和冲突。
		for k, m := range mset {
			if _, found := base[k]; !found {
				// 字段在此深度与同名方法冲突。
				if fset[k] {
					m = nil // 冲突
				}
				if base == nil {
					base = make(methodSet)
				}
				base[k] = m
			}
		}

		// 如果不存在具有匹配名称的条目，则将此深度处的所有（剩余）字段添加为冲突（因为它们将
		// 隐藏更深的任何方法）。
		for k := range fset {
			if _, found := base[k]; !found {
				if base == nil {
					base = make(methodSet)
				}
				base[k] = nil // 冲突
			}
		}

		current = consolidateMultiples(next)
	}

	if len(base) == 0 {
		return &emptyMethodSet
	}

	// 收集方法
	var list []*Selection
	for _, m := range base {
		if m != nil {
			m.recv = T
			list = append(list, m)
		}
	}
	// 按唯一名称排序
	sort.Slice(list, func(i, j int) bool {
		return list[i].obj.Id() < list[j].obj.Id()
	})
	return &MethodSet{list}
}

// 方法集是一组方法和名称冲突。
// 冲突表示出现了多个具有相同唯一id的方法，或具有该id的字段。
type methodSet map[string]*Selection // nil项表示名称冲突

// Add将列表中的所有函数添加到方法集s。
// 如果设置了倍数，则列表中的每个函数都会多次出现
// 并被视为冲突。
func (s methodSet) add(list []*Func, index []int, indirect bool, multiples bool) methodSet {
	if len(list) == 0 {
		return s
	}
	for i, f := range list {
		s = s.addOne(f, concat(index, i), indirect, multiples)
	}
	return s
}

func (s methodSet) addOne(f *Func, index []int, indirect bool, multiples bool) methodSet {
	if s == nil {
		s = make(methodSet)
	}
	key := f.Id()
	// 如果f不在集合中，则添加它
	if !multiples {
		// TODO（gri）可能无法添加找到的方法，因为它不在方法集合
		// /（！indirect&&f.hasptrecv（））。同一级别的第二种方法可能在方法
		// 集合中，并且可能不会与第一种方法冲突，从而导致误报。
		// 可能吗？侦查
		if _, found := s[key]; !found && (indirect || !f.hasPtrRecv()) {
			s[key] = &Selection{MethodVal, nil, f, index, indirect}
			return s
		}
	}
	s[key] = nil // 碰撞
	return s
}
